package com.crazymakercircle.http;

import com.crazymakercircle.httpclient.LongConnHttpClient;
import com.crazymakercircle.httpclient.NettyConfig;
import com.crazymakercircle.httpclient.common.TypeReference;
import com.crazymakercircle.httpclient.protocol.HttpStringCodec;
import com.crazymakercircle.httpclient.protocol.ReqOptions;
import io.netty.buffer.PooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.crazymakercircle.util.HttpAsyncClientUtil.getHttpAsyncClient;

/**
 * @author 40岁老架构师 尼恩 @ 公众号 技术自由圈
 * 1.   nohup java  -jar   /vagrant/CoccurrentDemos-1.0-SNAPSHOT.jar -e  *HttpAsyncClient >out.log 2>&1 &
 * 2.
 * @一句话介绍： liux下执行
 */
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@Warmup(iterations = 1, time = 20, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 20, timeUnit = TimeUnit.SECONDS)
@Threads(8) //Threads注解用于指定使用多少个线程来执行基准测试方法，如果使用@Threads指定线程数为2，那么每次测量都会创建两个线程来执行基准测试方法。
@Fork(0) // Fork用于指定fork出多少个子进程来执行同一基准测试方法。假设我们不需要多个进程，那么 可以使用@Fork指定为进程数为1。
@OutputTimeUnit(TimeUnit.SECONDS)
@Slf4j
@State(Scope.Benchmark)
public class TestAsyncClient {


    static final HttpClient httpClient = HttpClientBuilder.create().build();

    @Benchmark
    @Test
    public void HttpClient() {
        List<String> apis = Arrays.asList(
                "http://192.168.56.121/echo?i=1",
                "http://192.168.56.121/echo?i=2",
                "http://192.168.56.121/echo?i=3"
        );

        CountDownLatch latch = new CountDownLatch(apis.size());
        Map<String, String> results = new ConcurrentHashMap<>();
        for (String api : apis) {
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                HttpResponse response = null;
                try {
                    response = httpClient.execute(new HttpGet(api));
                } catch (IOException e) {
                    return null;
                }

                if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                    try {
                        return EntityUtils.toString(response.getEntity());
                    } catch (Exception e) {
                        log.error("error", e);
                        throw new RuntimeException(e);
                    }
                }
                return null;
            });
            future.whenComplete((result, throwable) -> {
                        latch.countDown();
                        results.put(api, result);
                    }
            );
        }

        try {
            latch.await(10000000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("error", e);

//            throw new RuntimeException(e);
        }

//        System.out.println(results.toString());
    }

    static CloseableHttpAsyncClient asyncClient
            = getHttpAsyncClient();

    @Benchmark
    @Test
    public void HttpAsyncClient() {

        List<String> urls = Arrays.asList(
                "http://192.168.56.121/echo?i=1",
                "http://192.168.56.121/echo?i=2",
                "http://192.168.56.121/echo?i=3"
        );

        CountDownLatch latch = new CountDownLatch(urls.size());
        Map<String, String> results = new ConcurrentHashMap<>();

        for (int i = 0; i < urls.size(); i++) {
            final String url = urls.get(i);
            Future<HttpResponse> f0 = asyncClient.execute(new HttpGet(url), new FutureCallback<HttpResponse>() {
                public void completed(HttpResponse response) {

                    latch.countDown();

                    if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                        try {
                            String result = EntityUtils.toString(response.getEntity());
                            results.put(url, result);
                        } catch (IOException e) {
                            log.error("error", e);
//                            throw new RuntimeException(e);
                        }
                    }

                }

                public void failed(Exception ex) {
                    latch.countDown();
                    log.error("error", ex);
//                    ex.printStackTrace();
                }

                public void cancelled() {
                    latch.countDown();
                }
            });
        }


        try {
            latch.await(10000000, TimeUnit.MILLISECONDS);
        } catch (
                InterruptedException e) {
            throw new RuntimeException(e);
        }

//        System.out.println("results = " + results);
    }
/*    @Benchmark
    public void testStringAdd() {
        String a = "";
        for (int i = 0; i < 10; i++) {
            a += i;
        }
    }*/


    /**
     * @author 40岁老架构师 尼恩 @ 公众号 技术自由圈
     * @详细介绍： 使用Setup和TearDown时，在默认情况下，Setup和TearDown会在一个基准方法的所有批次执行前后分别执行，
     * 如果需要在每一个批次或者每一次基准方法调用执行的前后执行对应的套件方法，则需要对@Setup和@TearDown进行简单的配置
     * <p>
     * Trial：Setup和TearDown默认的配置，该套件方法会在每一个基准测试方法的所有批次执行的前后被执行。
     * Iteration：由于我们可以设置Warmup和Measurement，因此每一个基准测试方法都会被执行若干个批次，如果想要在每一个基准测试批次执行的前后调用套件方法，则可以将Level设置为Iteration。
     * Invocation：将Level设置为Invocation意味着在每一个批次的度量过程中，每一次对基准方法的调用前后都会执行套件方法。
     */

    @Setup(Level.Trial)
    public static void setup() {


        if (null != config) return;

        asyncClient.start();

        System.out.println("setup = 初始化 .................................................. ");
        try {

            config = new NettyConfig();
            config.setRemoteHost("192.168.56.121");
            config.setMaxWaitingReSendReq(10000);

            codec = new HttpStringCodec(new PooledByteBufAllocator(true),
                    false, "192.168.56.121");
            longConnHttpClient = new LongConnHttpClient(config, codec);
        } catch (Exception e) {
            log.error("setup 初始化 error :", e);
        }
    }

    static HttpStringCodec codec = null;
    static NettyConfig config = null;
    static LongConnHttpClient longConnHttpClient = null;

    @Benchmark
    @Test
    public void nettyGet() {

//        setup();
        List<String> urls = Arrays.asList(
                "/echo?i=1",
                "/echo?i=2",
                "/echo?i=3"
        );

        CountDownLatch latch = new CountDownLatch(urls.size());
        Map<Integer, Object> results = new ConcurrentHashMap<>();


        ReqOptions options = new ReqOptions(TypeReference.from(String.class));
        for (int i = 0; i < urls.size(); i++) {

            int finalI = i;
            longConnHttpClient.getAsync("/echo?i=" + i, options, response -> {

                Object content = response.content();
                results.put(finalI, content);

                latch.countDown();
            }, e -> {
                e.printStackTrace();
                latch.countDown();
            });

        }

        try {
            latch.await(10000000, TimeUnit.MILLISECONDS);
        } catch (
                InterruptedException e) {
            throw new RuntimeException(e);
        }
//        System.out.println("results = " + results);

    }

    static int count = 6;

    @TearDown
    public static void stop() {
        if (count-- != 0) return;
        System.out.println("setup = stop .................................................. ");

        try {
            asyncClient.close();
            longConnHttpClient.close();
        } catch (IOException e) {
            log.error("io erro :", e);
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(TestAsyncClient.class.getSimpleName())
                .output("D:/Benchmark.log")
                .build();
        new Runner(options).run();
    }

    @org.junit.Test
    public void get() {

        NettyConfig config = new NettyConfig();
        config.setRemoteHost("192.168.56.121");
        config.setMaxWaitingReSendReq(10000);
        ReqOptions options = new ReqOptions(TypeReference.from(String.class));
        HttpStringCodec codec = new HttpStringCodec(new PooledByteBufAllocator(true),
                false, "192.168.56.121");
        longConnHttpClient = new LongConnHttpClient(config, codec);


        int num = 100000;
        CountDownLatch latch = new CountDownLatch(num);
        com.crazymakercircle.httpclient.HttpResponse httpResponse = longConnHttpClient.get("/echo", options);
        print(httpResponse.content());
        AtomicInteger errs = new AtomicInteger(0);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            longConnHttpClient.getAsync("/echo", options, response -> {
//                print(response.content());
                latch.countDown();
            }, e -> {
                e.printStackTrace();
                errs.incrementAndGet();
                latch.countDown();
            });

        }
        try {
            latch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("-------------" + (System.currentTimeMillis() - startTime));
        System.out.println(errs.get() + "-------------" + num / ((System.currentTimeMillis() - startTime) / 1000));
    }

    private void print(Object response) {
        System.out.println(response);
    }


}